package com.zc.core.server.session;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import com.zc.core.device.DeviceOperator;
import com.zc.core.message.codec.EncodedMessage;
import com.zc.core.message.codec.Transport;
import com.zc.core.utils.Reactors;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 子设备会话
 */
@Slf4j
public class ChildrenDeviceSession implements DeviceSession {

    /**
     * 会话id
     */
    @Getter
    private final String id;

    /**
     * 设备id
     */
    @Getter
    private final String deviceId;

    /**
     * 父设备会话
     */
    @Getter
    private final DeviceSession parent;

    /**
     * 设备操作对象
     */
    @Getter
    private final DeviceOperator operator;

    /**
     * 可关闭的会话
     */
    private List<Runnable> closeListener;

    /**
     * 最近会话时间
     */
    private long lastKeepAliveTime;

    /**
     * 保活超时时间
     */
    private long keepAliveTimeOutMs = -1;

    public ChildrenDeviceSession(String deviceId, DeviceSession parent, DeviceOperator operator) {
        this.id = deviceId;
        this.parent = parent;
        this.operator = operator;
        this.deviceId = deviceId;
        // 最近会话时间，同步自父设备
        this.lastKeepAliveTime = parent.lastPingTime();
    }

    /**
     * 获取父设备操作对象
     * @return 操作对象
     */
    public DeviceOperator getParentDevice() {
        return parent.getOperator();
    }

    /**
     * 最后的心跳时间
     * @return 时间
     */
    @Override
    public long lastPingTime() {
        return lastKeepAliveTime;
    }

    /**
     * 获取设备连接时间
     * @return 时间
     */
    @Override
    public long connectTime() {
        // 同步自父设备
        return parent.connectTime();
    }

    /**
     * 发送已编码的消息至设备
     * @param encodedMessage 消息
     * @return 发送成功与否
     */
    @Override
    public Mono<Boolean> send(EncodedMessage encodedMessage) {
        log.info("send child device[{}:{}] message", parent.getDeviceId(), deviceId);
        return parent.send(encodedMessage);
    }

    /**
     * 获取传输协议
     * @return 传输协议
     */
    @Override
    public Transport getTransport() {
        return parent.getTransport();
    }

    /**
     * 关闭会话
     */
    @Override
    public void close() {
        if (null != closeListener) {
            closeListener.forEach(Runnable::run);
        }
    }

    /**
     * 发送心跳
     */
    @Override
    public void ping() {
        parent.ping();
        this.lastKeepAliveTime = System.currentTimeMillis();
    }

    /**
     * 会话是否存活
     * @return 会话是否存活结果
     */
    @Override
    public boolean isAlive() {
        return aliveByKeepAlive() && parent.isAlive();
    }

    /**
     * 会话是否存活
     * @return 会话是否存活结果
     */
    private boolean aliveByKeepAlive() {
        return keepAliveTimeOutMs <= 0
                || System.currentTimeMillis() - lastKeepAliveTime < keepAliveTimeOutMs;
    }

    /**
     * 设置close回调
     * @param call 回调
     */
    @Override
    public synchronized void onClose(Runnable call) {
        if (closeListener == null) {
            closeListener = new CopyOnWriteArrayList<>();
        }
        closeListener.add(call);
    }

    /**
     * @return 会话连接的服务ID
     */
    @Override
    public Optional<String> getServerId() {
        return parent.getServerId();
    }

    /**
     * 获取客户端地址
     * @return 客户端地址
     */
    @Override
    public Optional<InetSocketAddress> getClientAddress() {
        return parent.getClientAddress();
    }

    /**
     * 设备保活超时时间
     * @param timeout 心跳超时时间
     */
    @Override
    public void setKeepAliveTimeout(Duration timeout) {
        keepAliveTimeOutMs = timeout.toMillis();
    }

    /**
     * @return 保活超时时间
     */
    @Override
    public Duration getKeepAliveTimeout() {
        return Duration.ofMillis(keepAliveTimeOutMs);
    }

    /**
     * 判断会话是否包装自指定的类型,在某些场景下,可能会对会话进行包装
     *
     * @param type 类型
     * @return 是否包装自指定的类型
     */
    @Override
    public boolean isWrapFrom(Class<?> type) {
        return type.isInstance(this) || parent.isWrapFrom(type);
    }

    /**
     * 展开为指定对会话类型,通过此方法拿到最原始对会话对象进行操作.如果类型不一致可能会抛出{@link ClassCastException}
     *
     * @param type 类型
     * @param <T>  类型泛型
     * @return 指定类型对会话
     */
    @Override
    public <T extends DeviceSession> T unwrap(Class<T> type) {
        return type.isInstance(this) ? type.cast(this) : parent.unwrap(type);
    }

    /**
     * 异步判断session是否存活
     *
     * @return async result
     * @since 1.20
     */
    @Override
    public Mono<Boolean> isAliveAsync() {
        //心跳超时
        if (!aliveByKeepAlive()) {
            return Reactors.ALWAYS_FALSE;
        }
        //判断上级
        return parent.isAliveAsync();
    }

    /**
     * 判断会话当前会话与另外一个会话是否发生了变化
     *
     * @param another 另外一个会话
     * @return 是否发生变化
     */
    @Override
    public boolean isChanged(DeviceSession another) {
        return parent.isChanged(another);
    }

    @Override
    public String toString() {
        return "children device[" + deviceId + "] in " + parent;
    }
}
